﻿#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
#else
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
#endif

namespace FreshMeat.Xaml
{
   /// <summary>
   /// The DropMenu can be described as a ToggleButton combined with a ComboBox.
   /// The button displays the <see cref="Header"/> along with a small "down-arrow" to the right.
   /// When the button is clicked, a menu drops down containing one or more <see cref="DropMenuItem"/>s,
   /// each of which can be clicked to perform a command.
   /// </summary>
   [TemplatePartAttribute(Name = "DropDownToggle", Type = typeof(ToggleButton))]
   [TemplatePartAttribute(Name = "Popup", Type = typeof(Popup))]
   [TemplatePartAttribute(Name = "ScrollViewer", Type = typeof(ScrollViewer))]
   public class DropMenu : ItemsControl
   {
#if !SILVERLIGHT && !NETFX_CORE
      static DropMenu()
      {
         DefaultStyleKeyProperty.OverrideMetadata(typeof(DropMenu), new FrameworkPropertyMetadata(typeof(DropMenu)));
      }
#endif

      /// <summary>
      /// Maximum height of the drop-down menu.
      /// </summary>
      public readonly static DependencyProperty MaxMenuHeightProperty = DependencyProperty.Register(
         "MaxMenuHeight",
         typeof(double),
         typeof(DropMenu),
         new PropertyMetadata(double.PositiveInfinity));

      /// <summary>
      /// Maximum width of the drop-down menu.
      /// </summary>
      public readonly static DependencyProperty MaxMenuWidthProperty = DependencyProperty.Register(
         "MaxMenuWidth",
         typeof(double),
         typeof(DropMenu),
         new PropertyMetadata(double.PositiveInfinity));

      /// <summary>
      /// Minimum width of the drop-down menu.
      /// </summary>
      public readonly static DependencyProperty MinMenuWidthProperty = DependencyProperty.Register(
         "MinMenuWidth",
         typeof(double),
         typeof(DropMenu),
         new PropertyMetadata(0.0));

      /// <summary>
      /// Horizontal offset of the drop-down menu from the <see cref="DropMenu"/> control.
      /// This defaults to 0 which means the left side of the menu will be aligned with the left side of the <see cref="DropMenu"/>.
      /// Negative values move the menu to the left, positive values move it to the right.
      /// </summary>
      public readonly static DependencyProperty MenuHorizontalOffsetProperty = DependencyProperty.Register(
         "MenuHorizontalOffset",
         typeof(double),
         typeof(DropMenu),
         new PropertyMetadata(0.0));

      /// <summary>
      /// The content that appears in the button portion of the control.
      /// </summary>
      public readonly static DependencyProperty HeaderProperty = DependencyProperty.Register(
         "Header",
         typeof(object),
         typeof(DropMenu),
         new PropertyMetadata(null));

#if SILVERLIGHT
      public readonly static DependencyProperty ItemContainerStyleProperty = DependencyProperty.Register(
         "ItemContainerStyle",
         typeof(Style),
         typeof(DropMenu),
         new PropertyMetadata(null));
#endif

      public double MaxMenuHeight
      {
         get { return (double)GetValue(MaxMenuHeightProperty); }
         set { SetValue(MaxMenuHeightProperty, value); }
      }

      public double MaxMenuWidth
      {
         get { return (double)GetValue(MaxMenuWidthProperty); }
         set { SetValue(MaxMenuWidthProperty, value); }
      }

      public double MinMenuWidth
      {
         get { return (double)GetValue(MinMenuWidthProperty); }
         set { SetValue(MinMenuWidthProperty, value); }
      }

      public double MenuHorizontalOffset
      {
         get { return (double)GetValue(MenuHorizontalOffsetProperty); }
         set { SetValue(MenuHorizontalOffsetProperty, value); }
      }

      public object Header
      {
         get { return GetValue(HeaderProperty); }
         set { SetValue(HeaderProperty, value); }
      }

      ToggleButton dropDownToggle;
      Popup popup;
      ScrollViewer scrollViewer;
      DropMenuItem currentItem;
#if SILVERLIGHT || NETFX_CORE
      FrameworkElement root;
#endif

      /// <summary>
      /// Creates default instance.
      /// </summary>
      public DropMenu()
      {
         DefaultStyleKey = typeof(DropMenu);
      }

      /// <summary>
      /// Applies the template.
      /// </summary>
#if NETFX_CORE
      protected override void OnApplyTemplate()
#else
      public override void OnApplyTemplate()
#endif
      {
         base.OnApplyTemplate();

         dropDownToggle = GetTemplateChild("DropDownToggle") as ToggleButton;
         popup = GetTemplateChild("Popup") as Popup;
         scrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer;

         // All of these must be exist in the template or we can't do anything useful.
         if (dropDownToggle != null && popup != null && scrollViewer != null)
         {
            dropDownToggle.Checked += DropDownToggleChecked;

#if SILVERLIGHT || NETFX_CORE
            // Get the root so we can hook events later on
            root = VisualHelper.GetRoot(this);
#else
            // Make the WPF popup more similar to the Silverlight popup.
            popup.PlacementTarget = dropDownToggle;
            popup.Placement = PlacementMode.Bottom;
            popup.StaysOpen = false;
            popup.AllowsTransparency = true;
#endif

            popup.Closed += (sender, args) => dropDownToggle.IsChecked = false;
         }
      }

      void DropDownToggleChecked(object sender, RoutedEventArgs routedEventArgs)
      {
         var isChecked = dropDownToggle.IsChecked.GetValueOrDefault();
         if (!isChecked)
            return;

         // WPF uses PlacementMode.Bottom so the top of the popup will be at the bottom of the dropdownToggle.
         // Silverlight has no PlacementMode so the top of the menu is at the TOP of the dropdownToggle.
         // Here we correct so they both end up at the same place.
#if SILVERLIGHT || NETFX_CORE
         popup.VerticalOffset = dropDownToggle.ActualHeight;
#else
         popup.VerticalOffset = 0;
#endif

         popup.HorizontalOffset = MenuHorizontalOffset;

         currentItem = null;

         OpenPopup();
      }

      void OpenPopup()
      {
         popup.IsOpen = true;

#if SILVERLIGHT
         root.MouseLeftButtonDown += RootMouseLeftButtonDown;
#elif NETFX_CORE
         root.PointerPressed += RootMouseLeftButtonDown;
#endif
      }

      void ClosePopup()
      {
         popup.IsOpen = false;

#if SILVERLIGHT
         root.MouseLeftButtonDown -= RootMouseLeftButtonDown;
#elif NETFX_CORE
         root.PointerPressed -= RootMouseLeftButtonDown;
#endif
      }

#if SILVERLIGHT || NETFX_CORE
      // Silverlight's Popup can't be set to automatically close itself when the user clicks 
      // anywhere outside it, so we use both these event handlers to close it when appropriate.

      // If the user clicks on an inactive area such as a border or disabled control then the 
      // root element will detect that as a MouseLeftButtonDown, so we close the popup.
#if NETFX_CORE
      void RootMouseLeftButtonDown(object sender, PointerRoutedEventArgs e)
#else
      void RootMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
#endif
      {
         ClosePopup();
      }

      // If the user clicks on another active control then we will lose focus, so we close the popup.
      protected override void OnLostFocus(RoutedEventArgs e)
      {
         if (e.OriginalSource == dropDownToggle || e.OriginalSource == popup)
            ClosePopup();

         base.OnLostFocus(e);
      }
#endif

      protected override DependencyObject GetContainerForItemOverride()
      {
         var container = new DropMenuItem {Owner = this};

         if (ItemContainerStyle != null)
            container.Style = ItemContainerStyle;

         return container;
      }

      protected override bool IsItemItsOwnContainerOverride(object item)
      {
         return item is DropMenuItem;
      }

      protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
      {
         var container = element as DropMenuItem;
         if (container != null)
            container.Owner = this;

         base.PrepareContainerForItemOverride(element, item);
      }

#if SILVERLIGHT
      public Style ItemContainerStyle
      {
         get { return (Style)GetValue(ItemContainerStyleProperty); }
         set { SetValue(ItemContainerStyleProperty, value); }
      }
#endif

      public void ItemMouseEnter(DropMenuItem item)
      {
         ResetItemStates(currentItem); 
         currentItem = item;
         VisualStateManager.GoToState(currentItem, "MouseOver", true);
      }

      public void ItemMouseLeave(DropMenuItem item)
      {
         ResetItemStates(currentItem);
         currentItem = item;
         VisualStateManager.GoToState(currentItem, "Normal", true);
      }

      public void ItemGotFocus(DropMenuItem item)
      {
         ResetItemStates(currentItem);
         currentItem = item;
         VisualStateManager.GoToState(currentItem, "Focused", true);
      }

      public void ItemLostFocus(DropMenuItem item)
      {
         ResetItemStates(currentItem);
         currentItem = item;
         VisualStateManager.GoToState(currentItem, "Unfocused", true);
      }

      public void ItemMouseDown(DropMenuItem item)
      {
         ResetItemStates(currentItem);
         currentItem = item;
         VisualStateManager.GoToState(currentItem, "Selected", true);
      }

      public void ItemMouseUp(DropMenuItem item)
      {
         ResetItemStates(currentItem);
         currentItem = item;
         VisualStateManager.GoToState(currentItem, "Unselected", true);

         ClosePopup();

         if (item.Command != null && item.Command.CanExecute(item.CommandParameter))
            item.Command.Execute(item.CommandParameter);
      }

      void ResetItemStates(DropMenuItem item)
      {
         if (item == null)
            return;

         VisualStateManager.GoToState(item, "Unselected", true);
         VisualStateManager.GoToState(item, "Unfocused", true);
         VisualStateManager.GoToState(this, "Normal", true);
      }
   }
}
